package hd.util.xml;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.ByteArrayInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
 * xml解析
 * <p/> 使用反射原理解析xml
 * Created by Haidy on 15/4/13.
 */
public class XmlParser<T> {

    private final Class clazz;
    private String xml;

    public XmlParser(String xml, Class clazz) {
        this.xml = xml;
        this.clazz = clazz;
    }

    public T parserData(String findNode) {
        T t;
        try {
            t = (T) clazz.newInstance();
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(new ByteArrayInputStream(xml.getBytes()));
            Element root = document.getDocumentElement();
            NodeList nodeList = root.getElementsByTagName(findNode);
            if (nodeList.getLength() > 0) {
                NodeList tList = nodeList.item(0).getChildNodes();
                for (int i = 0; i < tList.getLength(); i++) {
                    Node node = tList.item(i);
                    if (node.getNodeType() == Node.ELEMENT_NODE) {
                        String name = node.getNodeName();
                        System.out.println(name);
                        String value = node.hasChildNodes() ? node.getFirstChild().getNodeValue() : null;
                        Field field = clazz.getDeclaredField(name);
                        if (field != null) {
                            setValue(node, field, t, value);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
        return t;
    }

    /**
     * 解析
     *
     * @param findNode 查询的节点
     */
    public List<T> parser(String findNode) {
        List<T> list = new ArrayList<>();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(new ByteArrayInputStream(xml.getBytes()));
            Element root = document.getDocumentElement();
            NodeList nodeList = root.getElementsByTagName(findNode);
            for (int i = 0; i < nodeList.getLength(); i++) {
                NodeList childNodes = nodeList.item(i).getChildNodes();
                T t = (T) clazz.newInstance();
                for (int j = 0; j < childNodes.getLength(); j++) {
                    Node node = childNodes.item(j);
                    if (node.getNodeType() == Node.ELEMENT_NODE) {
                        String name = node.getNodeName();
                        String value = node.hasChildNodes() ? node.getFirstChild().getNodeValue() : null;
                        Field field = clazz.getDeclaredField(name);
                        if (field != null) {
                            setValue(node, field, t, value);
                        }
                    }
                }
                list.add(t);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
        return list;
    }

    /**
     * 设置Value
     *
     * @param node  node
     * @param field field
     * @param obj   被设置值得对象
     * @param value value
     * @throws IllegalAccessException exception
     */
    private void setValue(Node node, Field field, Object obj, String value) throws IllegalAccessException {
        field.setAccessible(true);
        if (field.getType().equals(String.class)) {//String类型
            field.set(obj, value);
        } else if (field.getType().equals(int.class)) {//int类型
            field.setInt(obj, Integer.parseInt(value));
        } else if (field.getType().equals(float.class)) {//int类型
            field.setFloat(obj, Float.parseFloat(value));
        } else if (field.getType().equals(double.class)) {
            field.setDouble(obj, Double.parseDouble(value));
        } else if (field.getType().equals(long.class)) {
            field.setLong(obj, Long.parseLong(value));
        } else if (field.getType().equals(boolean.class)) {
            field.setBoolean(obj, Boolean.parseBoolean(value));
        } else {//自定义类型
            if (isArray(node.getChildNodes())) {
                List<Object> list = new ArrayList<>();
                parser(getGeneric(field), node.getChildNodes(), list);
                field.set(obj, list);
            } else {
                field.set(obj, parser(field.getType(), node.getChildNodes()));
            }
        }
    }

    private Class getGeneric(Field field) {
        if (field.getType().isAssignableFrom(List.class)) {
            Type type = field.getGenericType();
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return (Class) parameterizedType.getActualTypeArguments()[0];
        }
        return null;
    }

    /**
     * 解析Node
     *
     * @param clazz    class
     * @param nodeList nodeList
     */
    private Object parser(Class clazz, NodeList nodeList) {
        try {
            Object object = clazz.newInstance();
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    String value = node.hasChildNodes() ? node.getFirstChild().getNodeValue() : null;
                    Field field = clazz.getDeclaredField(node.getNodeName());
                    if (field != null) {
                        setValue(node, field, object, value);
                    }
                }
            }
            return object;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 解析数组
     *
     * @param clazz    class
     * @param nodeList nodeList
     * @param list     保存的List
     */
    private void parser(Class clazz, NodeList nodeList, List<Object> list) {
        try {
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    NodeList childNodes = node.getChildNodes();
                    Object object = clazz.newInstance();
                    for (int j = 0; j < childNodes.getLength(); j++) {
                        Node childNode = childNodes.item(j);
                        if (childNode.getNodeType() == Node.ELEMENT_NODE && !childNode.getNodeName().equals("#text")) {
                            String value = childNode.hasChildNodes() ? childNode.getFirstChild().getNodeValue() : null;
                            Field field = clazz.getDeclaredField(childNode.getNodeName());
                            if (field != null) {
                                setValue(node, field, object, value);
                            }
                        }
                    }
                    list.add(object);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 判断是否为数组
     *
     * @param nodeList nodeList
     * @return 是否为数组
     */
    private boolean isArray(NodeList nodeList) {
        String front = null;
        if (nodeList.getLength() > 1) {
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    if (front != null) {
                        if (!front.equals(node.getNodeName())) {
                            return false;
                        }
                    } else {
                        front = node.getNodeName();
                    }
                }
            }
        }
        return true;
    }

}
